package compiler.semantics;

import compiler.syntax_tree.*;

public class SemanticAnalyzer
{
    private MainClass mainClass;
    public static SymbolicTable symbolicTable;

    public SemanticAnalyzer(MainClass mainClass)
    {
        this.mainClass = mainClass;
    }

    public void analyze()
            throws Exception
    {
        initSymbolicTable();
        checkStatement(mainClass.statement);
    }

    private void initSymbolicTable()
            throws Exception
    {
        symbolicTable = new SymbolicTable();
        VarDeclarationList varDeclarations = mainClass.varDeclarations;
        for (int i = 0; i < varDeclarations.size(); i++)
        {
            VarDeclaration varDeclaration = varDeclarations.elementAt(i);
            String varName = varDeclaration.id.name;
            Type varType = varDeclaration.type;

            if (symbolicTable.containsKey(varName))
            {
                dispatchError("Semantic error: Variable " + varName + " is already declared");
            }
            else
            {
                VarType varTypeId = null;
                if (varType instanceof IntegerType)
                {
                    varTypeId = VarType.INTEGER;
                }
                else if (varType instanceof BooleanType)
                {
                    varTypeId = VarType.BOOLEAN;
                }
                else if (varType instanceof IntArrayType)
                {
                    varTypeId = VarType.INT_ARRAY;
                }
                symbolicTable.put(varName, varTypeId);
            }
        }
    }

    private void checkStatement(Statement statement)
            throws Exception
    {
        if (statement instanceof Block)
        {
            checkBlock((Block) statement);
        }
        else if (statement instanceof Print)
        {
            checkExpression(((Print) statement).expression);
        }
        else if (statement instanceof If)
        {
            checkIf((If) statement);
        }
        else if (statement instanceof While)
        {
            checkWhile((While) statement);
        }
        else if (statement instanceof Assignment)
        {
            checkAssignment((Assignment) statement);
        }
        else if (statement instanceof ArrayAssign)
        {
            checkArrayAssign((ArrayAssign) statement);
        }
    }

    private void checkWhile(While whileStatement)
            throws Exception
    {
        if (!isLogicalExpression(whileStatement.condition))
        {
            dispatchError("While condition must be of type boolean");
        }
        checkExpression(whileStatement.condition);
        checkStatement(whileStatement.statement);
    }

    private void checkArrayAssign(ArrayAssign arrayAssign)
            throws Exception
    {
        Identifier variable = arrayAssign.id;
        Expression indexExpression = arrayAssign.indexExpression;
        checkArrayIndex(indexExpression);
        Expression expr = arrayAssign.assignedExpression;
        checkExpression(variable);
        checkExpression(indexExpression);
        checkExpression(expr);
        String varName = variable.name;
        if ((symbolicTable.getSymbolType(varName) != VarType.INT_ARRAY) ||
                (!isArithmeticExpression(indexExpression)) || (!isArithmeticExpression(expr)))
        {
            dispatchError("Incorrect array assignment");
        }
    }

    private void checkArrayIndex(Expression indexExpression)
            throws Exception
    {
        if (indexExpression instanceof IntegerLiteral)
        {
            if (((IntegerLiteral) indexExpression).value < 0)
            {
                dispatchError("Array index can't be negative");
            }
        }
    }

    private void checkAssignment(Assignment assignment)
            throws Exception
    {
        Identifier variable = assignment.id;
        Expression expr = assignment.expression;
        String varName = variable.name;
        checkExpression(variable);
        checkExpression(expr);
        boolean areTypesCompatible = false;
        switch (symbolicTable.getSymbolType(varName))
        {
            case INTEGER:
                areTypesCompatible = isArithmeticExpression(expr);
                break;
            case BOOLEAN:
                areTypesCompatible = isLogicalExpression(expr);
                break;
            case INT_ARRAY:
                areTypesCompatible = isIntArrayExpression(expr);
                break;
        }
        if (!areTypesCompatible)
        {
            dispatchError("Incompatible types of assignment operator operands");
        }
    }

    private void checkIf(If ifStatement)
            throws Exception
    {
        Expression condition = ifStatement.condition;
        if (!isLogicalExpression(condition))
        {
            dispatchError("Wrong type of IF condition");
        }
        checkExpression(condition);
        checkStatement(ifStatement.ifStatement);
        Statement elseStatement = ifStatement.elseStatement;
        if (elseStatement != null)
        {
            checkStatement(elseStatement);
        }
    }

    private void checkBlock(Block block)
            throws Exception
    {
        StatementList statements = block.statementList;
        for (int i = 0; i < statements.size(); i++)
        {
            Statement currentStatement = statements.elementAt(i);
            checkStatement(currentStatement);
        }
    }

    private void checkExpression(Expression expression)
            throws Exception
    {
        if (expression instanceof Identifier)
        {
            String varName = ((Identifier) expression).name;
            if (!symbolicTable.containsKey(varName))
            {
                dispatchError("Semantic error: Variable " + varName + " is not declared");
            }
        }
        else if (expression instanceof Plus)
        {
            Expression op1 = ((Plus) expression).op1;
            Expression op2 = ((Plus) expression).op2;
            if (!isArithmeticExpression(op1) || !isArithmeticExpression(op2))
            {
                dispatchError("Incompatible operand types of '+' operation");
            }
            checkExpression(op1);
            checkExpression(op2);
        }
        else if (expression instanceof Minus)
        {
            Expression op1 = ((Minus) expression).op1;
            Expression op2 = ((Minus) expression).op2;
            if (!isArithmeticExpression(op1) || !isArithmeticExpression(op2))
            {
                dispatchError("Incompatible operand types of '-' operation");
            }
            checkExpression(op1);
            checkExpression(op2);
        }
        else if (expression instanceof UnaryMinus)
        {
            Expression expr = ((UnaryMinus) expression).expression;
            checkExpression(expr);
            if (!isArithmeticExpression(expr))
            {
                dispatchError("Incompatible operand type of '-' operation");
            }
        }
        else if (expression instanceof Multiply)
        {
            Expression op1 = ((Multiply) expression).op1;
            Expression op2 = ((Multiply) expression).op2;
            if (!isArithmeticExpression(op1) || !isArithmeticExpression(op2))
            {
                dispatchError("Incompatible operand types of '*' operation");
            }
            checkExpression(op1);
            checkExpression(op2);
        }
        else if (expression instanceof Div)
        {
            Expression op1 = ((Div) expression).op1;
            Expression op2 = ((Div) expression).op2;
            if (!isArithmeticExpression(op1) || !isArithmeticExpression(op2))
            {
                dispatchError("Incompatible operand types of '/' operation");
            }
            checkDivisionByZero(op2);
            checkExpression(op1);
            checkExpression(op2);
        }
        else if (expression instanceof Mod)
        {
            Expression op1 = ((Mod) expression).op1;
            Expression op2 = ((Mod) expression).op2;
            if (!isArithmeticExpression(op1) || !isArithmeticExpression(op2))
            {
                dispatchError("Incompatible operand types of '/' operation");
            }
            checkDivisionByZero(op2);
            checkExpression(op1);
            checkExpression(op2);
        }
        else if (expression instanceof And)
        {
            Expression op1 = ((And) expression).op1;
            Expression op2 = ((And) expression).op2;
            if (!isLogicalExpression(op1) || !isLogicalExpression(op2))
            {
                dispatchError("Incompatible operand types of '&&' operation");
            }
            checkExpression(op1);
            checkExpression(op2);
        }
        else if (expression instanceof Or)
        {
            Expression op1 = ((Or) expression).op1;
            Expression op2 = ((Or) expression).op2;
            if (!isLogicalExpression(op1) || !isLogicalExpression(op2))
            {
                dispatchError("Incompatible operand types of '||' operation");
            }
            checkExpression(op1);
            checkExpression(op2);
        }
        else if (expression instanceof Not)
        {
            Expression op = ((Not) expression).expression;
            if (!isLogicalExpression(op))
            {
                dispatchError("Operand of '!' operation must be of logical type");
            }
            checkExpression(op);
        }
        else if (expression instanceof LessThan)
        {
            Expression op1 = ((LessThan) expression).op1;
            Expression op2 = ((LessThan) expression).op2;
            if (!isArithmeticExpression(op1) || !isArithmeticExpression(op2))
            {
                dispatchError("Incompatible operand types of '<' operation");
            }
            checkExpression(op1);
            checkExpression(op2);
        }
        else if (expression instanceof NewArray)
        {
            Expression initializer = ((NewArray) expression).expression;
            if (initializer instanceof IntegerLiteral)
            {
                if (((IntegerLiteral) initializer).value <= 0)
                {
                    dispatchError("Incorrect array size");
                }
            }
            if (!isArithmeticExpression(initializer))
            {
                dispatchError("Incorrect type of array initializer");
            }
            checkExpression(initializer);
        }
    }

    private void checkDivisionByZero(Expression operand)
            throws Exception
    {
        if (operand instanceof IntegerLiteral)
        {
            if (((IntegerLiteral) operand).value == 0)
            {
                dispatchError("Arithmetic error: division by zero");
            }
        }
    }

    public static boolean isArithmeticExpression(Expression expression)
    {
        boolean isArithmeticExpression = false;
        if ((expression instanceof Plus)     || (expression instanceof Minus) ||
            (expression instanceof Multiply) || (expression instanceof IntegerLiteral) ||
            (expression instanceof Div) || (expression instanceof Mod) ||
            (expression instanceof ArrayLookup) || (expression instanceof UnaryMinus))
        {
            isArithmeticExpression = true;
        }
        else if (expression instanceof Identifier)
        {
            String varName = ((Identifier) expression).name;
            VarType varType = symbolicTable.getSymbolType(varName);
            if (varType == VarType.INTEGER)
            {
                isArithmeticExpression = true;
            }
        }
        return isArithmeticExpression;
    }

    public static boolean isLogicalExpression(Expression expression)
    {
        boolean isLogicalExpression = false;
        if ((expression instanceof LessThan) || (expression instanceof And) || (expression instanceof Or) ||
            (expression instanceof False) || (expression instanceof True) || (expression instanceof Not))
        {
            isLogicalExpression = true;
        }
        else if (expression instanceof Identifier)
        {
            String varName = ((Identifier) expression).name;
            VarType varType = symbolicTable.getSymbolType(varName);
            if (varType == VarType.BOOLEAN)
            {
                isLogicalExpression = true;
            }
        }
        return isLogicalExpression;
    }

    public static boolean isIntArrayExpression(Expression expression)
    {
        boolean isIntArrayExpression = false;
        if (expression instanceof NewArray)
        {
            isIntArrayExpression = true;
        }
        else if (expression instanceof Identifier)
        {
            String varName = ((Identifier) expression).name;
            VarType varType = symbolicTable.getSymbolType(varName);
            if (varType == VarType.INT_ARRAY)
            {
                isIntArrayExpression = true;
            }
        }
        return  isIntArrayExpression;
    }
    
    private void dispatchError(String errorMessage)
            throws Exception
    {
        throw new Exception(errorMessage);
    }

}
